/**
 * 
 */
package org.stars.daostars.core.conditions;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.security.UserIdentity;
import org.stars.util.StringUtility;
import org.stars.util.reflect.ReflectUtil;

import static org.stars.daostars.core.conditions.ConditionConjunctionType.AND;
import static org.stars.daostars.core.conditions.ConditionConjunctionType.OR;

/**
 * Questa classe rappresenta l'insieme delle condizioni che i parametri di una
 * query devno soddisfare per poter considerare un codice condizionale.
 * 
 * @author Francesco Benincasa (908099)
 * 
 */
public class Condition implements Serializable {

	/**
	 * serial id
	 */
	private static final long serialVersionUID = 6109792879304555690L;

	/**
	 * Recupera logger
	 * 
	 * @return
	 * 		logger
	 */
	public Log getLog()
	{
		return LogFactory.getLog(Condition.class);
	}
	

	private String name;

	private ArrayList<ConditionConstraint> parameters;

	private Set<String> roles;

	private Set<String> functions;

	private ConditionConjunctionType type;

	/**
	 * Costruttore. Crea i parametri e i ruoli. Per default tutti i ruoli
	 * possono accedere alla condizione.
	 */
	public Condition() {
		type = AND;
		roles = new TreeSet<String>();
		functions = new TreeSet<String>();
		parameters = new ArrayList<ConditionConstraint>();

		setRoles("*"); // per default è accessibile da tutti
		setFunctions("*"); // per default è accessibile da qualsiasi funzione
	}

	/**
	 * Aggiunge un ruolo alla clausola condizionale. Nel caso in cui il ruolo
	 * che si vuole aggiungere è una lista di ruoli separati da virgola, essi
	 * vengono suddivisi ed inseriti singolarmente.
	 * 
	 * @param role
	 *            ruolo da aggiungere sotto forma di stringa.
	 */
	public void addRoles(String role) {
		String[] temp = StringUtility.splitList(role);

		for (String app : temp) {
			roles.add(app);
		}
	}

	/**
	 * Imposta le funzioni, azzerandole prima di inserire nuovamente.
	 * 
	 * @param value
	 *            funzione da inserire.
	 */
	public void setFunctions(String value) {
		functions.clear();
		addFunctions(value);
	}

	/**
	 * Aggiunge un ruolo alla clausola condizionale. Nel caso in cui il ruolo
	 * che si vuole aggiungere è una lista di ruoli separati da virgola, essi
	 * vengono suddivisi ed inseriti singolarmente.
	 * 
	 * @param function
	 *            funzione da aggiungere sotto forma di stringa.
	 */
	public void addFunctions(String function) {
		String[] temp = StringUtility.splitList(function);

		for (String app : temp) {
			functions.add(app);
		}
	}

	/**
	 * Getter dell'attributo name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Getter dell'attributo parameters.
	 * 
	 * @return the parameters
	 */
	public ArrayList<ConditionConstraint> getParameters() {
		return parameters;
	}

	/**
	 * Getter dell'attributo roles.
	 * 
	 * @return the roles
	 */
	public Set<String> getRoles() {
		return roles;
	}

	/**
	 * Getter dell'attributo functions.
	 * 
	 * @return the functions
	 */
	public Set<String> getFunctions() {
		return functions;
	}

	/**
	 * Getter dell'attributo type.
	 * 
	 * @return the type
	 */
	public ConditionConjunctionType getType() {
		return type;
	}

	/**
	 * Indica se la condizione è soggetta alla sicurezza o meno
	 * 
	 * @return <code>true</code> se la condizione è soggetta a qualche ruolo o
	 *         funzione che l'utente deve ricoprire per avere tale condizione
	 * 
	 */
	public boolean isSecurized() {
		if ((roles == null || roles.isEmpty() || roles.contains("*"))
				&& (functions == null || functions.isEmpty() || functions
						.contains("*"))) {
			return false;
		}

		return true;
	}

	/**
	 * Dato un utente, ed i relativi privilegi, un entitybean che contiene i
	 * parametri nominali della query e un array di parametri che rappresenta i
	 * parametri numerativi della query, questo metodo verifica se siamo nelle
	 * condizioni di poter applicare tale condizione.
	 * <p>
	 * Prima di tutto vengono verificati i privilegi dell'utente. I privilegi si
	 * misurano sia in termini di ruoli sia in termini di funzioni alle quali
	 * l'utente è abilitato. Possono esistere più profili e funzioni alle quali
	 * è associata la condizione.
	 * <p>
	 * Nel caso in cui l'utente non è definito ma non è stato definito alcun
	 * vincolo sulla sicurezza, i controlli legati alle funzioni ed ai ruoli 
	 * vengono ignorati. Questo è dovuto al fatto che per default, nel caso
	 * in cui non venga specificato altro, tutte le query condizionate sono
	 * abilitate ad essere eseguite con  funzione * e ruolo * (ovvero tutte le 
	 * funzioni ed i ruoli). 
	 * <p>
	 * In un secondo momento vengono verificate le condizioni legate ai valori
	 * contenuti dalle proprietà. Possono esistere più condizioni sulle
	 * variabli.
	 * 
	 * @param user
	 *            utente che sta provando ad accedere allo dao
	 * @param eb
	 *            entity bean che contiene i parametri nominativi
	 * @param sqlParameters
	 *            parametri numerativi
	 * @return <code>true</code> se è possibile applicare la condizione,
	 *         <code>false</code> altrimenti.
	 */
	public boolean isVerified(UserIdentity user, Object eb,
			Object[] sqlParameters, Map<String, String> variables) throws Exception {
		
		boolean bRet = false;

		try {
			// 1 - vediamo se l'utente ha i privilegi necessari
			// se e solo se la user identity è not null			
			String roleApp;
			String functionApp;
			// se l'utente non è definito, e ci sono ruoli, può eseguirlo
			// se non c'è l'utente e non ci
			if (roles.contains("!*") || functions.contains("!*")) {
				// ASSERT: !* vuol dire che nulla è abilitato, quindi possiamo uscire
				return false;
			}
			
			// Controlliamo i ruoli, nel caso in cui non abbiamo abilitato tutti i ruoli
			if (!roles.contains("*"))
			{
				if (user==null)
				{
					// ASSERT: l'utente non è definito, ma la condizione
					// richiede un ruolo o una funzione
					// Da tenere presente che per default le funzioni ed i ruoli sono
					// impostati a *
					return false;
				}
				
				// controlliamo i ruoli
				for (String role : roles) {
					roleApp = StringUtility.nvl(role);
					if (role.startsWith("!")) {
						roleApp = role.substring(1);							
						if (user.hasRole(roleApp)) return false;							
					} else {
						if (!user.hasRole(roleApp)) return false;							
					}
				}
			}

			// Controlliamo le funzioni, nel caso in cui non abbiamo abilitato tutte le funzioni
			if (!functions.contains("*")) {
				if (user==null)
				{
					// ASSERT: l'utente non è definito, ma la condizione
					// richiede un ruolo o una funzione
					// Da tenere presente che per default le funzioni ed i ruoli sono
					// impostati a *
					return false;
				}

				// controlliamo le funzioni
				for (String function : functions) {
					functionApp = StringUtility.nvl(function);
					if (function.startsWith("!")) {
						functionApp = function.substring(1);							
						if (user.isEnableTo(functionApp)) return false;							
					} else {
						if (!user.isEnableTo(functionApp)) return false;							
					}
				}
			}
						
			// 2 - vediamo se i parametri hanno i valori giusti.
			if (getType() == AND) {
				bRet = true;
				for (ConditionConstraint constraint : parameters) {
					bRet = bRet && constraint.isVerified(user, eb, sqlParameters, variables);

					if (!bRet)
						return false;
				}
				return true;
			} else if (getType() == OR) {
				bRet = false;
				for (ConditionConstraint constraint : parameters) {
					bRet = bRet || constraint.isVerified(user, eb, sqlParameters, variables);
				}
			}

		} catch (Exception e) {
			Log log=getLog();
			log.error("An exception occured during the executing of the condition "
							+ this.name + ". Exception: " + e.toString());
			bRet = false;
			throw(e);
		}
		return bRet;
	}

	/**
	 * Setter dell'attributo name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Setter dell'attributo parameters.
	 * 
	 * @param parameters
	 *            the parameters to set
	 */
	public void setParameters(ArrayList<ConditionConstraint> parameters) {
		this.parameters = parameters;
	}

	public void addParameter(ConditionConstraint value) {
		parameters.add(value);
	}

	/**
	 * Setter dell'attributo roles.
	 * 
	 * @param roles
	 *            the roles to set
	 */
	public void setRoles(Set<String> roles) {
		this.roles = roles;
	}

	/**
	 * Imposta i ruoli, azzerandoli prima di inserire nuovamente.
	 * 
	 * @param value
	 *            ruoli da inserire.
	 */
	public void setRoles(String value) {
		roles.clear();
		addRoles(value);
	}

	/**
	 * Setter dell'attributo type.
	 * 
	 * @param type
	 *            the type to set
	 */
	public void setType(ConditionConjunctionType type) {
		this.type = type;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectUtil.inspectObject(this);
	}

}
